package com.zbb.jacocoserver.service;

import com.zbb.jacocoserver.common.CompressUtil;
import com.zbb.jacocoserver.common.Const;
import com.zbb.jacocoserver.common.IdGenerator;
import com.zbb.jacocoserver.common.MavenInvoke;
import com.zbb.jacocoserver.model.BuildInfo;
import com.zbb.jacocoserver.model.CodeResources;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.maven.shared.utils.StringUtils;
import org.eclipse.jgit.api.errors.GitAPIException;

import org.jacoco.core.analysis.Analyzer;
import org.jacoco.core.analysis.CoverageBuilder;
import org.jacoco.core.analysis.IBundleCoverage;
import org.jacoco.core.data.ExecutionDataWriter;
import org.jacoco.core.internal.diff.GitAdapter;
import org.jacoco.core.runtime.RemoteControlReader;
import org.jacoco.core.runtime.RemoteControlWriter;
import org.jacoco.core.tools.ExecFileLoader;
import org.jacoco.report.DirectorySourceFileLocator;
import org.jacoco.report.FileMultiReportOutput;
import org.jacoco.report.IReportVisitor;
import org.jacoco.report.MultiSourceFileLocator;
import org.jacoco.report.html.HTMLFormatter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Slf4j
@Service
public class JacocoService {

    @Autowired
    private BuildInfoService buildInfoService;

    private int port = 6300;//默认的agent统一的端口号
    private boolean reset = false;//默认不重置直接append
    private String jacocoBaseDir = "E:\\jacocoWorkSpace";



    @Async
    public void executeForAllCoverage(String gitAccount, String gitPassword, String gitAddress, String branch,
                                      String projectAddress, List<String> modules, String buildId) throws IOException, GitAPIException {

        BuildInfo buildInfo = new BuildInfo();
        String id = IdGenerator.uuid();
        buildInfo.setId(id);
        buildInfo.setBuildId(buildId);
        buildInfo.setStatus(Const.EXECUTING);
        buildInfoService.insertSelective(buildInfo);
        //1.先判断是否要clone remote git
        //git登录授权
        GitAdapter.setCredentialsProvider(gitAccount, gitPassword);
        String projectName = getProjectName(gitAddress);
        String repositoryDirectory = Paths.get("e:","demo",projectName).toString();
        cloneRepository(repositoryDirectory,gitAddress,branch);

        //2.maven编译源码
        String pomPath = Paths.get(repositoryDirectory,"pom.xml").toString();
        List<String> goals = Arrays.asList("clean","compile");
        String mavenHome = Paths.get("d:","apache-maven-3.5.3").toString();
        MavenInvoke mavenInvoke = new MavenInvoke();
        mavenInvoke.mavenBuild(pomPath,goals,mavenHome);
        //3.处理java跟class路径
        CodeResources codeResources = new CodeResources();
        List<File> sourceDirs = new ArrayList<>();
        List<File> classesDirs = new ArrayList<>();
        if(modules != null && !modules.isEmpty()){
            for (String module : modules) {
                File sourceDir = Paths.get(repositoryDirectory,module,"src","main","java").toFile();
                File classesDir = Paths.get(repositoryDirectory,module,"target","classes").toFile();
                sourceDirs.add(sourceDir);
                classesDirs.add(classesDir);
            }
        }else{
            File sourceDir = Paths.get(repositoryDirectory,"src","main","java").toFile();
            File classesDir = Paths.get(repositoryDirectory,"target","classes").toFile();
            sourceDirs.add(sourceDir);
            classesDirs.add(classesDir);
        }
        codeResources.setSourceDirectories(sourceDirs);
        codeResources.setClassesDirectories(classesDirs);


        //4.开始生成报告
        log.info("buildId:{}",buildId);
        File baseDir = Paths.get(jacocoBaseDir,buildId).toFile();
        if (!baseDir.exists()){
            baseDir.mkdirs();
        }
        String destFile = Paths.get(jacocoBaseDir,buildId,"jacoco.exec").toString();


        dump(destFile,projectAddress,port,reset);
        File reportDirectory = Paths.get(jacocoBaseDir,buildId,"report").toFile();
        if (!reportDirectory.exists()){
            reportDirectory.mkdirs();
        }
        CoverageBuilder coverageBuilder = new CoverageBuilder();
        create(new File(destFile),codeResources,reportDirectory,projectName,coverageBuilder);
        //5.压缩文件
        String targetFolderPath = reportDirectory.toString();
        String rawZipFilePath = Paths.get(jacocoBaseDir,buildId,buildId + ".zip").toString();
        CompressUtil.compress(targetFolderPath,rawZipFilePath);
        //6.更新构建状态
        BuildInfo buildInfoEnd = buildInfoService.selectByPrimaryKey(id);
        buildInfoEnd.setStatus(Const.SUCCESS);
        String reportAddress =  "coverage\\download?buildId="+buildId;
        buildInfoEnd.setReportAddress(reportAddress);
        buildInfoService.updateSelective(buildInfoEnd);
    }

    @Async
    public void executeForAddCoverage(String gitAccount, String gitPassword, String gitAddress, String branch,
                                      String projectAddress, List<String> modules, String buildId,
                                      String newCommitId,String oldCommitId) throws IOException, GitAPIException {

        BuildInfo buildInfo = new BuildInfo();
        String id = IdGenerator.uuid();
        buildInfo.setId(id);
        buildInfo.setBuildId(buildId);
        buildInfo.setStatus(Const.EXECUTING);
        buildInfoService.insertSelective(buildInfo);
        //1.先判断是否要clone remote git
        //git登录授权
        GitAdapter.setCredentialsProvider(gitAccount, gitPassword);
        String projectName = getProjectName(gitAddress);
        String repositoryDirectory = Paths.get("e:","demo",projectName).toString();
        cloneRepository(repositoryDirectory,gitAddress,branch);

        //2.maven编译源码
        String pomPath = Paths.get(repositoryDirectory,"pom.xml").toString();
        List<String> goals = Arrays.asList("clean","compile");
        String mavenHome = Paths.get("d:","apache-maven-3.5.3").toString();
        MavenInvoke mavenInvoke = new MavenInvoke();
        mavenInvoke.mavenBuild(pomPath,goals,mavenHome);
        //3.处理java跟class路径
        CodeResources codeResources = new CodeResources();
        List<File> sourceDirs = new ArrayList<>();
        List<File> classesDirs = new ArrayList<>();
        if(modules != null && !modules.isEmpty()){
            for (String module : modules) {
                File sourceDir = Paths.get(repositoryDirectory,module,"src","main","java").toFile();
                File classesDir = Paths.get(repositoryDirectory,module,"target","classes").toFile();
                sourceDirs.add(sourceDir);
                classesDirs.add(classesDir);
            }
        }else{
            File sourceDir = Paths.get(repositoryDirectory,"src","main","java").toFile();
            File classesDir = Paths.get(repositoryDirectory,"target","classes").toFile();
            sourceDirs.add(sourceDir);
            classesDirs.add(classesDir);
        }
        codeResources.setSourceDirectories(sourceDirs);
        codeResources.setClassesDirectories(classesDirs);


        //4.开始生成报告
        log.info("buildId:{}",buildId);
        File baseDir = Paths.get(jacocoBaseDir,buildId).toFile();
        if (!baseDir.exists()){
            baseDir.mkdirs();
        }
        String destFile = Paths.get(jacocoBaseDir,buildId,"jacoco.exec").toString();


        dump(destFile,projectAddress,port,reset);
        File reportDirectory = Paths.get(jacocoBaseDir,buildId,"report").toFile();
        if (!reportDirectory.exists()){
            reportDirectory.mkdirs();
        }
        CoverageBuilder coverageBuilder = new CoverageBuilder(repositoryDirectory,branch,newCommitId,oldCommitId);
        create(new File(destFile),codeResources,reportDirectory,projectName,coverageBuilder);
        //5.压缩文件
        String targetFolderPath = reportDirectory.toString();
        String rawZipFilePath = Paths.get(jacocoBaseDir,buildId,buildId + ".zip").toString();
        CompressUtil.compress(targetFolderPath,rawZipFilePath);
        //6.更新构建状态
        BuildInfo buildInfoEnd = buildInfoService.selectByPrimaryKey(id);
        buildInfoEnd.setStatus(Const.SUCCESS);
        String reportAddress =  "coverage\\download?buildId="+buildId;
        buildInfoEnd.setReportAddress(reportAddress);
        buildInfoService.updateSelective(buildInfoEnd);
    }
    /**
     *
     * @param destFile dump到的目标文件路径
     * @param address 启动jacoco agent 的ip地址
     * @param port 启动jacoco agent 的端口
     * @param reset 是否需要重置上次的收集轨迹
     * @throws IOException
     */
    private void dump(String destFile,String address,int port,boolean reset) throws IOException {
        final FileOutputStream localFile = new FileOutputStream(destFile);
        final ExecutionDataWriter localWriter = new ExecutionDataWriter(
                localFile);
        //连接Jacoco服务
        final Socket socket = new Socket(InetAddress.getByName(address), port);
        final RemoteControlWriter writer = new RemoteControlWriter(socket.getOutputStream());
        final RemoteControlReader reader = new RemoteControlReader(socket.getInputStream());
        reader.setSessionInfoVisitor(localWriter);
        reader.setExecutionDataVisitor(localWriter);

        // 发送Dump命令，获取Exec数据
        writer.visitDumpCommand(true, reset);
        if (!reader.read()) {
            throw new IOException("Socket closed unexpectedly.");
        }
        socket.close();
        localFile.close();
    }

    /**
     * @param codeResources class文件跟java文件的集合
     * @param executionDataFile dump过来的exec文件
     * @param reportDirectory 要保存的报告的地址
     * @param title 报告的主题名字
     * @throws IOException
     */
    public void create(File executionDataFile,CodeResources codeResources,File reportDirectory,String title,
                       CoverageBuilder coverageBuilder) throws IOException {

        // Read the jacoco.exec file. Multiple data files could be merged
        // at this point
        ExecFileLoader execFileLoader = new ExecFileLoader();
        execFileLoader.load(executionDataFile);
        // Run the structure analyzer on a single class folder to build up
        // the coverage aa. The process would be similar if your classes
        // were in a jar file. Typically you would create a bundle for each
        // class folder and each jar you want in your report. If you have
        // more than one bundle you will need to add a grouping node to your
        // report
        IBundleCoverage bundleCoverage = analyzeStructure(execFileLoader,codeResources,title,coverageBuilder);

        createReport(bundleCoverage,execFileLoader,reportDirectory,codeResources);

    }

    private void createReport(IBundleCoverage bundleCoverage,ExecFileLoader execFileLoader,File reportDirectory,CodeResources codeResources)
            throws IOException {

        // Create a concrete report visitor based on some supplied
        // configuration. In this case we use the defaults
        final HTMLFormatter htmlFormatter = new HTMLFormatter();
        final IReportVisitor visitor = htmlFormatter.createVisitor(new FileMultiReportOutput(reportDirectory));

        // Initialize the report with all of the execution and session
        // information. At this point the report doesn't know about the
        // structure of the report being created
        visitor.visitInfo(execFileLoader.getSessionInfoStore().getInfos(),execFileLoader.getExecutionDataStore().getContents());

        // Populate the report structure with the bundle coverage information.
        // Call visitGroup if you need groups in your report.
       // visitor.visitBundle(bundleCoverage, new DirectorySourceFileLocator(sourceDirectory, "utf-8", 4));


	      //多源码路径
        MultiSourceFileLocator sourceLocator = new MultiSourceFileLocator(4);
        List<File> sourceDirectories = codeResources.getSourceDirectories();
        for (File sourceDirectory : sourceDirectories) {
            sourceLocator.add( new DirectorySourceFileLocator(sourceDirectory, "utf-8", 4));
        }
        visitor.visitBundle(bundleCoverage,sourceLocator);

        // Signal end of structure information to allow report to write all
        // information out
        visitor.visitEnd();

    }

    /**
     * 获取全量的
     * @param execFileLoader
     * @param codeResources
     * @param title
     * @return
     * @throws IOException
     */
    private IBundleCoverage analyzeStructure(ExecFileLoader execFileLoader,CodeResources codeResources,String title,
                                             CoverageBuilder coverageBuilder) throws IOException {


        //全量覆盖
		//final CoverageBuilder coverageBuilder = new CoverageBuilder();


        //基于分支比较覆盖，参数1：本地仓库，参数2：开发分支（预发分支），参数3：基线分支(不传时默认为master)
        //本地Git路径，新分支 第三个参数不传时默认比较maser，传参数为待比较的基线分支
        //final CoverageBuilder coverageBuilder = new CoverageBuilder("E:\\Git-pro\\JacocoTest","daily");
        //基于Tag比较的覆盖 参数1：本地仓库，参数2：代码分支，参数3：新Tag(预发版本)，参数4：基线Tag（变更前的版本）
        //final CoverageBuilder coverageBuilder = new CoverageBuilder("E:\\Git-pro\\JacocoTest","daily","v004","v003");


        final Analyzer analyzer = new Analyzer(execFileLoader.getExecutionDataStore(), coverageBuilder);
        List<File> classesDirectories = codeResources.getClassesDirectories();
        for (File classesDirectory : classesDirectories) {
            analyzer.analyzeAll(classesDirectory);
        }
        return coverageBuilder.getBundle(title);
    }
    public String cloneRepository(String repositoryDirectory,String repositoryUri,String branch) throws GitAPIException {
        if(StringUtils.isBlank(repositoryDirectory)
                ||StringUtils.isBlank(repositoryUri)
                ||StringUtils.isBlank(branch)){
            throw new IllegalArgumentException("Git argument exception");
        }
        if(!GitAdapter.checkRepositoryExists(repositoryDirectory)){
            File repositoryDirFile = new File(repositoryDirectory);
            if(repositoryDirFile.exists()){
                log.info("The git repositoryDirectory exists and will execute delete");
                FileUtils.deleteQuietly(repositoryDirFile);
            }
            log.info("The git repository already exists and will pull the source code");
            GitAdapter.cloneRepository(repositoryDirectory,repositoryUri,branch);
        }
        return repositoryDirectory;
    }


    private String getProjectName(String repositoryUri){
        return new File(repositoryUri).getName().split("\\.")[0];
    }

}
